import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.Iterator;

/**
 * ContextDefinition class contains all contexts defined.
 *  meaning all context characteristics
 * 
 * @author Yong Shin
 *
 */
public class ContextDefinition {
        static boolean DEBUG = true;
        
	/** Comments start with this character */
	private static final char COMMENT = '#';
	
	/** Flags if the contents of context characteristics definition file should be reloaded */
	private static boolean reload = true;
	
	/** Stores the name of context characteristics definition file */
	private static String ccdFile;
	
	/** Stores singleton instance of ContextDefinition */
	private static ContextDefinition contextDef;
	
	/**
	 * Maps context ID to context object.
	 */
	private HashMap<Integer, Context> contextMap;
	
	/**
	 * Loads ContextDefinitions parsed from specified CCD file.
	 * 
	 * @param ccdFile context characteristics definition file name.
	 */
	private void loadContextDefinitions(String ccdFile) {
		if (DEBUG) System.out.println("Loading context definitions from ccd file..");
		// read context characteristics file and parse it
		// each line of context characteristics file should be parsed into a single context
		BufferedReader bufread = null;
		int count = 0;
		try {
			bufread = new BufferedReader(new FileReader(ccdFile));
			String inputLine;
			while ( (inputLine = bufread.readLine()) != null ) {
				
				// trim leading and trailing white spaces
				inputLine.trim();
				
				// skip empty or commented lines
				if (inputLine.length() == 0 || inputLine.indexOf(COMMENT) == 0)
					continue;
				
				// create a new context using count as context id
				Context context = new Context(count);
				
				// parse context definition
				context.parse(inputLine);
				
				// put context in the map
				contextMap.put(count, context);
				
				count++;
			}
		if (DEBUG) System.out.println("...done loading context definitions");
                        
		} catch (IOException ex) {
			System.out.println(ex.getMessage());
		} finally {
			if (bufread != null) try {bufread.close(); } catch (Exception ex){}
		}	
	}
	
	/**
	 * Creates an instance of context definition and loads the content of
	 * context characteristics definition file.
	 */
	private ContextDefinition() {
		contextMap = new HashMap<Integer, Context>();
		loadContextDefinitions(ccdFile);
	}
	
	/**
	 * Returns Context object corresponding to context id.
	 * @param contextID
	 * @return
	 */
	public Context getContext(int contextID) {
		return contextMap.get(contextID);
	}
	
	/**
	 * Returns the number of contexts.
	 * @return
	 */
	public int getNumContexts() {
		return contextMap.size();
	}
	
	/**
	 * Returns all split criteria possible from context definitions.
	 * @return
	 */
	public TreeSet<SplitCriteria> getAllSplitCriteria() {
		TreeSet<SplitCriteria> criteria = new TreeSet<SplitCriteria>();
		Iterator<Context> contexts = contextMap.values().iterator();
		while (contexts.hasNext()) {
			Context context = contexts.next();
			// total number of split criteria should be one less than the
			// number of buckets
			for (int i=0; i < context.getNumberBuckets()-1; i++) {
				criteria.add(new SplitCriteria(context.getContextID(), i));
			}
		}
		return criteria;
	}


	/**
	 * Max # possible different contexts, as a double
	 * @return
	 */
    public double maxPossibleDifferentContextChars() {
		Iterator<Context> contexts = contextMap.values().iterator();
		double retval = 1.0;
		while (contexts.hasNext()) {
		    Context context = contexts.next();
		    retval *= context.getNumberBuckets();
		}
		return retval;
	}


	/**
	 * Dump content of context definition. For debugging.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		Iterator<Context> ctxIter = contextMap.values().iterator();
		while (ctxIter.hasNext()) {
			Context ctx = ctxIter.next();
			sb.append(ctx.toString());
			sb.append("\n");
		}
		return sb.toString();
	}
	
	/**
	 * Sets context characteristics definition file name.
	 * @param ccdFile context characteristics definition file name
	 */
	public static void setCCDFile(String ccdFile) {
		ContextDefinition.ccdFile = ccdFile;
		reload = true;
	}
	
	/**
	 * Returns singleton instance of ContextDefinition.
	 * @return
	 */
	public static ContextDefinition getContextDefinition() {
		if (contextDef == null || reload) {
			contextDef = new ContextDefinition();
			reload = false;
		}
		return contextDef;
	}

	/**
	 * Test ContextDefinition.
	 * @param args
	 */
	public static void main(String args[]) {
		ContextDefinition.setCCDFile("default.ccd");
		ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
		System.out.println(cxtdef);
	}
}
